/**
 * s_gmail.c
 * GMail source for TrayInfo.
 * GMail support added by Laszlo Ashin <kodest@gmail.com>
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> /* close() */
#include <netdb.h>

#include "trayinfo.h"
#include "utils.h"

#ifdef HAVE_LIBSSL
#include <openssl/ssl.h>
#include <openssl/rand.h>

#define GMAILHOST	"mail.google.com"
#define GMAILPORT	"443"
#define RECVBUFSIZE	512
#define SENDBUFSIZE	512

static int nmails = 0;

static SSL *ssl = NULL;
static SSL_CTX *ctx = NULL;

static char *auth;

static int
gmail_write(struct timeval *now, int fd)
{
	if (!nmails) return 0;
	return printf("gmail:%d", nmails);
}

static int
gmail_open()
{
	struct addrinfo hints;
	struct addrinfo *res, *rp;
	int fd, s;
	char buf[SENDBUFSIZE];
	int n;

	memset(&hints, 0, sizeof(hints));
	hints.ai_flags = AI_ADDRCONFIG;
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = 0;

	s = getaddrinfo(GMAILHOST, GMAILPORT, &hints, &res);
	if (s) {
		fprintf(stderr, "gmail: getaddrinfo(): %s\n", gai_strerror(s));
		return 0;
	}

	for (rp = res; rp; rp = rp->ai_next) {
		fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (fd == -1)
			continue;

		if (!connect(fd, rp->ai_addr, rp->ai_addrlen))
			break;

		close(fd);
	}

	freeaddrinfo(res);

	if (!rp) {
		fprintf(stderr, "gmail: unable to connect\n");
		return 0;
	}

	ctx = SSL_CTX_new(SSLv23_client_method());
	if (!ctx) {
		fprintf(stderr, "gmail: failed to create ssl context\n");
		goto err_clo;
	}

	ssl = SSL_new(ctx);
	if (!ssl) {
		fprintf(stderr, "gmail: failed to initialize ssl layer\n");
		goto err_ctx;
	}

	if (!SSL_set_fd(ssl, fd)) {
		fprintf(stderr, "gmail: ssl association failed\n");
		goto err_ssl;
	}

	RAND_poll();
	while (!RAND_status()) {
		unsigned short r = rand() & 0xffff;
		RAND_seed(&r, sizeof(r));
	}

	if (SSL_connect(ssl) != 1) {
		fprintf(stderr, "gmail: failed to make ssl connection\n");
		goto err_ssl;
	}

	n = snprintf(
		buf, sizeof(buf),
		"GET /mail/feed/atom HTTP/1.0\r\n"
		"Authorization: Basic %s\r\n"
		"Host: "GMAILHOST":"GMAILPORT"\r\n"
		"Connection: Close\r\n"
		"\r\n",
		auth
	);

	if (n < 0 || n >= sizeof(buf)) {
		fprintf(stderr, "gmail: sendbuf was too small\n");
		goto err_ssl;
	}

	SSL_write(ssl, buf, n);

	return fd;

err_ssl:
	SSL_free(ssl);
	ssl = NULL;
err_ctx:
	SSL_CTX_free(ctx);
	ctx = NULL;
err_clo:
	close(fd);
	return 0;
}

static void
gmail_close(int fd)
{
	if (ssl) SSL_shutdown(ssl);
	close(fd);
	if (ssl) SSL_free(ssl);
	ssl = NULL;
	if (ctx) SSL_CTX_free(ctx);
	ctx = NULL;
}

static void
gmail_process_recved(char *s)
{
	if (!strncmp(s, "fullcount>", 10)) {
		if (!sscanf(s + 10, "%d", &nmails))
			nmails = 0;
	}
}

static ssize_t
gmail_read(int fd)
{
	static char buf[RECVBUFSIZE];
	static size_t sord = 0; /* size of received data */
	ssize_t n;
	char *sep;
	
	if (!ssl) return 0;
	n = SSL_read(ssl, buf + sord, sizeof(buf) - sord);
	if (n <= 0) return n;
	sord += n;
	while ((sep = memchr(buf, '<', sord))) {
		int l;

		*sep++ = '\0';
		l = sep - buf;
		gmail_process_recved(buf);
		sord -= l;
		memmove(buf, buf + l, sord);
	}
	/* check for overflow */
	if (sord == sizeof(buf)) {
		/* simply drop everything */
		sord = 0;
	}
	return n;
}

static void
gmail_init(void)
{
	auth = NULL;
}

static void
gmail_done(void)
{
	if (auth) free(auth);
	auth = NULL;
}

static int
gmail_cfg_auth(const char *s)
{
	if (auth) free(auth);
	auth = base64_encode(s, strlen(s));

	return 0;
}

void
regsrc_gmail(src_t *src)
{
	src->name = "gmail";
	src->init = gmail_init;
	src->done = gmail_done;
	src->write = gmail_write;
	src->open = gmail_open;
	src->close = gmail_close;
	src->read = gmail_read;

	src->wait_retry = 5 * 60;

	cfg_add_handler("gmail-auth", gmail_cfg_auth);
}

#endif /* HAVE_LIBSSL */
